/**
 * Copyright (C) 2008 Mathieu Carbou <mathieu.carbou@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.code.xmltool;

import com.google.code.xmltool.util.ValidationResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.namespace.NamespaceContext;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import java.io.OutputStream;
import java.io.Writer;
import java.net.URL;

/**
 * @author Mathieu Carbou (mathieu.carbou@gmail.com)
 */
public interface XMLTag {

    ////////////////// BUILD METHODS - add* //////////////////////


    /**
     * Add a namespace to the document
     *
     * @param prefix       The prefix of the namespace
     * @param namespaceURI The URI of the namespace
     * @return this
     */
    XMLTag addNamespace(String prefix, String namespaceURI);

    /**
     * Create a tag under the current location and use it as the current node
     *
     * @param name Name of the element to add
     * @return this
     */
    XMLTag addTag(String name);

    /**
     * Create a new attribute for the current node
     *
     * @param name  Name of the attribute to add
     * @param value value of the attribute to add
     * @return this
     */
    XMLTag addAttribute(String name, String value);

    /**
     * Set the text to place in the current node. The current node becomes the parent node of the node containing the text.
     * I.e.
     * <code>&lt;tag>some text&lt;/tag></code>
     *
     * @param text the text to add
     * @return this
     */
    XMLTag addText(String text);

    /**
     * Set the CDATAto place in the current node. The current node becomes the parent node of the node containing the data.
     * I.e.
     * <code>&lt;tag>&lt;![CDATA[data section]]>&lt;/tag></code>
     *
     * @param data the text to add
     * @return this
     */
    XMLTag addCDATA(String data);

    ////////////////// NAVIGATION METHODS - goto* //////////////////////


    /**
     * Go to parent tag. Do nothing if we are already at root
     *
     * @return this
     */
    XMLTag gotoParent();

    /**
     * Go to document root tag
     *
     * @return this
     */
    XMLTag gotoRoot();

    /**
     * Go to a specific node
     *
     * @param relativeXpath XPath expresion
     * @param arguments     to be replaced in xpath expression before compiling
     * @return this
     * @throws com.google.code.xmltool.XMLDocumentException
     *          if the node does not exist or if the XPath expression is invalid
     */
    XMLTag gotoTag(String relativeXpath, Object... arguments) throws XMLDocumentException;

    /**
     * Go to the child element of the curent node.
     *
     * @return this
     * @throws com.google.code.xmltool.XMLDocumentException
     *          If the current node has several childs or no child at all
     */
    XMLTag gotoChild() throws XMLDocumentException;

    /**
     * Go to the Nth child of the curent node.
     *
     * @param i index of the child, from 1 to child element number
     * @return this
     * @throws com.google.code.xmltool.XMLDocumentException
     *          If the child node does not exist
     */
    XMLTag gotoChild(int i) throws XMLDocumentException;

    /**
     * Go to the child found with given node name
     *
     * @param nodeName name of the child to find.
     * @return this
     * @throws com.google.code.xmltool.XMLDocumentException
     *          If the element with this name has not been found or if there are too many elements
     */
    XMLTag gotoChild(String nodeName) throws XMLDocumentException;

    ////////////////// EXISTENCE CHECKS - has* //////////////////////

    /**
     * Check if a tag exist in the document
     *
     * @param relativeXpath XPath expression where the tag should be located
     * @param arguments     XPath arguments
     * @return true if the tag is exist
     */
    boolean hasTag(String relativeXpath, Object... arguments);

    /**
     * Check if targeted tag has an attribute of given name
     *
     * @param name          the name of the attribute
     * @param relativeXpath XPath that target the tag
     * @param arguments     optional arguments of xpath expressiion
     * @return true if the tag exist with this attribute name
     */
    boolean hasAttribute(String name, String relativeXpath, Object... arguments);

    /**
     * Check wheter current tag contains an atribute
     *
     * @param name Attribute name
     * @return true if the attribute is in current tag
     */
    boolean hasAttribute(String name);

    ////////////////// CLOSURE - for* //////////////////////


    /**
     * Execute an action for each child in the current node.
     *
     * @param callBack Callback method to run after the current tag of the document has changed to a child
     * @return this
     */
    XMLTag forEachChild(CallBack callBack);

    /**
     * Execute an action for each selected tags in the current node.
     *
     * @param callBack      Callback method to run after the current tag of the document has changed to a child
     * @param relativeXpath XXath expression that select the node
     * @param arguments     facultative Xpath expression arguments
     * @return this
     */
    XMLTag forEach(CallBack callBack, String relativeXpath, Object... arguments);

    ////////////////// DATA ACCESS METHODS - raw xpath * //////////////////////

    /**
     * Execute an XPath expression directly using the Java XPath API
     * @param relativeXpath The XPath expression
     * @param arguments Arguments to replace in the expression using String.format()
     * @return The {@link javax.xml.xpath.XPathConstants#STRING} return type
     */
    String rawXpathString(String relativeXpath, Object... arguments);

    /**
     * Execute an XPath expression directly using the Java XPath API
     * @param relativeXpath The XPath expression
     * @param arguments Arguments to replace in the expression using String.format()
     * @return The {@link javax.xml.xpath.XPathConstants#NUMBER} return type
     */
    Number rawXpathNumber(String relativeXpath, Object... arguments);

    /**
     * Execute an XPath expression directly using the Java XPath API
     * @param relativeXpath The XPath expression
     * @param arguments Arguments to replace in the expression using String.format()
     * @return The {@link javax.xml.xpath.XPathConstants#BOOLEAN} return type
     */
    Boolean rawXpathBoolean(String relativeXpath, Object... arguments);

    /**
     * Execute an XPath expression directly using the Java XPath API
     * @param relativeXpath The XPath expression
     * @param arguments Arguments to replace in the expression using String.format()
     * @return The {@link javax.xml.xpath.XPathConstants#NODE} return type
     */
    Node rawXpathNode(String relativeXpath, Object... arguments);

    /**
     * Execute an XPath expression directly using the Java XPath API
     * @param relativeXpath The XPath expression
     * @param arguments Arguments to replace in the expression using String.format()
     * @return The {@link javax.xml.xpath.XPathConstants#NODESET} return type
     */
    NodeList rawXpathNodeSet(String relativeXpath, Object... arguments);

    ////////////////// DATA ACCESS METHODS - get* //////////////////////

    /**
     * @return the current tag
     */
    Element getCurrentTag();

    /**
     * @return the current tag name
     */
    String getCurrentTagName();

    /**
     * @return the namespace context
     */
    NamespaceContext getContext();

    /**
     * Get the prefix of a namespace
     *
     * @param namespaceURI The URI of the namespace
     * @return the prefix or ""
     */
    String getPefix(String namespaceURI);

    /**
     * Get all bound prefixes of a namespace
     *
     * @param namespaceURI The URI of the namespace
     * @return a list of prefixes or an empty array.
     */
    String[] getPefixes(String namespaceURI);

    /**
     * @return The text content of the current node, "" if none
     */
    String getText();

    /**
     * Get the text of a sepcific node
     *
     * @param relativeXpath XPath expression that select the node
     * @param arguments     facultative Xpath expression arguments
     * @return the text of "" if none
     * @throws com.google.code.xmltool.XMLDocumentException
     *          If the XPath expression is not valid or if the node does not exist
     */
    String getText(String relativeXpath, Object... arguments) throws XMLDocumentException;

    /**
     * @return The CDATA content of the current node, "" if none
     */
    String getCDATA();

    /**
     * Get the CDATA of a selected node
     *
     * @param relativeXpath XPath expression that select the node
     * @param arguments     facultative Xpath expression arguments
     * @return the text of "" if none
     * @throws com.google.code.xmltool.XMLDocumentException
     *          If the XPath expression is invalid or if the node does not exist
     */
    String getCDATA(String relativeXpath, Object... arguments) throws XMLDocumentException;

    /**
     * @return all attribute names of current node
     */
    String[] getAttributeNames();

    /**
     * returns the attribute value of the current node
     *
     * @param name attribute name
     * @return attribute value or "" if no attribute
     * @throws com.google.code.xmltool.XMLDocumentException
     *          If the attribute does not exist
     */
    String getAttribute(String name) throws XMLDocumentException;

    /**
     * Returns the attribute value of the node pointed by given XPath expression
     *
     * @param name          attribute name
     * @param relativeXpath XPath expression
     * @param arguments     XPath expression parameters, replaced using same format as String.format()
     * @return attribute value
     * @throws com.google.code.xmltool.XMLDocumentException
     *          Attribute does not exist or XPath expression is invalid
     */
    String getAttribute(String name, String relativeXpath, Object... arguments) throws XMLDocumentException;

    ////////////////// DELETE / CHANGE METHODS - get* //////////////////////


    /**
     * Delete all existing elements of this node
     *
     * @return this
     */
    XMLTag deleteChilds();

    /**
     * Delete current tag and its childs. If the current tag is the root tag of xml document, it cannot be
     * delete. It can just be replaced. In this case, an exception is thrown
     *
     * @return this
     * @throws com.google.code.xmltool.XMLDocumentException
     *          if the current node is the root node
     */
    XMLTag delete() throws XMLDocumentException;

    /**
     * Delete all existing attributes of current node
     *
     * @return this
     */
    XMLTag deleteAttributes();

    /**
     * Delete an attribute of the current node.
     *
     * @param name attribute name
     * @return this
     * @throws com.google.code.xmltool.XMLDocumentException
     *          if teh attribute does not exist
     */
    XMLTag deleteAttribute(String name) throws XMLDocumentException;

    /**
     * Replace current element name by another name
     *
     * @param newTagName New name of the tag
     * @return this
     */
    XMLTag renameTo(String newTagName);

    ////////////////// TRANSFORMATION METHODS - get* //////////////////////


    /**
     * @return the Document
     */
    Document toDocument();

    /**
     * @return a string representation of the document in UTF-8 (Unicode)
     */
    String toString();

    /**
     * @param encoding destination encoding of XML document
     * @return a string representation of the document
     */
    String toString(String encoding);

    byte[] toBytes();

    byte[] toBytes(String encoding);

    /**
     * Converts this document to the result provided
     *
     * @param out The output result
     * @return this
     */
    XMLTag toResult(Result out);

    /**
     * Converts this document to the result provided, overriding default encoding of xml document
     *
     * @param out      The output result
     * @param encoding The new encoding
     * @return this
     */
    XMLTag toResult(Result out, String encoding);

    XMLTag toStream(OutputStream out);

    XMLTag toStream(OutputStream out, String encoding);

    XMLTag toStream(Writer out);

    XMLTag toStream(Writer out, String encoding);

    ////////////////// VALIDATION METHODS - get* //////////////////////


    /**
     * Validate this document against specifief schemas
     *
     * @param schemas A list of schemas
     * @return A validation result object containing exception list occured if any
     */
    ValidationResult validate(Source... schemas);

    /**
     * Validate this document against specifief schemas
     *
     * @param schemaLocations A list of schemas
     * @return A validation result object containing exception list occured if any
     */
    ValidationResult validate(URL... schemaLocations);

}